package org.infodavid.common.impl.persistence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.PersistenceException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.joda.time.DateTime;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.ejb.ApplicationSettingsBean;
import org.infodavid.common.impl.services.ServiceUtil;
import org.infodavid.common.model.IApplicationSettings;
import org.infodavid.common.model.IGenericPropertiesMap;
import org.infodavid.common.model.IGenericProperty;
import org.infodavid.common.model.IOrganization;
import org.infodavid.common.model.criteria.IApplicationSettingsCriteria;
import org.infodavid.common.model.criteria.IGenericPropertyCriteria;
import org.infodavid.common.model.impl.HibernateHelper;
import org.infodavid.common.persistence.ApplicationSettingsDataService;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.exceptions.NoDataFoundException;
import org.infodavid.common.persistence.impl.DataServiceHelper;
import org.infodavid.common.persistence.impl.PersistenceUtil;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class ApplicationSettingsDataServiceImpl.
 */
@AImplementation(value = ApplicationSettingsDataService.class, runtime = ERuntime.DEFAULT)
public final class ApplicationSettingsDataServiceImpl extends
    AbstractApplicationSettingsDataService {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(AbstractApplicationSettingsDataService.class);

  /** The helper. */
  private final DataServiceHelper<IApplicationSettings,IApplicationSettings,Long> helper;

  /**
   * The Constructor.
   */
  private ApplicationSettingsDataServiceImpl() {
    super();

    helper = new DataServiceHelper<IApplicationSettings,IApplicationSettings,Long>(this);
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.persistence.IDataService#getEntityClass()
   */
  public Class<? extends IApplicationSettings> getEntityClass() {
    return ApplicationSettingsBean.class;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.ApplicationSettingsDataService#findByProduct(org.infodavid.common
   * .persistence.PersistenceSession, java.lang.String)
   */
  @Override
  public IApplicationSettings findByProduct(final PersistenceSession session, final String value)
      throws PersistenceException, ServiceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by unique product using session = " + session + ", value = " + value);
    }

    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(1);

    params.get(0).setName(IApplicationSettings.PRODUCT).setValue(value);

    final List<IApplicationSettings> results =
        session.findEntities(IApplicationSettings.class, QUERY_FIND_BY_PRODUCT, params);

    return results == null || results.isEmpty() ? null : results.get(0);
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.ApplicationSettingsDataService#findFirst(org.infodavid.common.
   * persistence.PersistenceSession)
   */
  @Override
  public IApplicationSettings findFirst(final PersistenceSession session)
      throws PersistenceException, ServiceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching first settings using session = " + session);
    }

    PersistenceUtil.validate(session);

    return (IApplicationSettings)session.executeQuery(IApplicationSettings.class, QUERY_FIND, null,
        true);
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.ApplicationSettingsDataService#findOrganization(org.infodavid.
   * common.persistence.PersistenceSession)
   */
  @Override
  public IOrganization findOrganization(final PersistenceSession session)
      throws PersistenceException, ServiceException {
    final IApplicationSettings settings = findFirst(session);

    return settings == null ? null : settings.getOrganization();
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List, org.infodavid.common.model.criteria.ISearchCriteria)
   */
  @SuppressWarnings("unchecked")
  public long find(final PersistenceSession session, final List<IApplicationSettings> entities,
      final IApplicationSettingsCriteria criteria) throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching by criteria using session = " + session + ", entities = " + entities
          + ", criteria = " + criteria);
    }

    PersistenceUtil.validate(session);
    helper.validate(entities);

    final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
    final HibernateHelper hbnHelper = HibernateHelper.getInstance();
    final Criteria hbnCriteria = hbnHelper.createCriteria(session, ApplicationSettingsBean.class);
    final Criteria hbnCountCriteria =
        hbnHelper.createCriteria(session, ApplicationSettingsBean.class);

    if (criteria == null) {
      hbnCriteria.add(Restrictions.eq(IApplicationSettings.ARCHIVED, Boolean.FALSE));
      hbnCountCriteria.add(Restrictions.eq(IApplicationSettings.ARCHIVED, Boolean.FALSE));
    }
    else {
      final Boolean archived = criteria.isArchived();
      final String product = criteria.getProductNamePattern();

      if (StringUtils.isNotEmpty(product)) {
        hbnCriteria.add(Restrictions.like(IApplicationSettings.PRODUCT, product));
        hbnCountCriteria.add(Restrictions.like(IApplicationSettings.PRODUCT, product));
      }

      if (archived == null) {
        hbnCriteria.add(Restrictions.eq(IApplicationSettings.ARCHIVED, Boolean.FALSE));
        hbnCountCriteria.add(Restrictions.eq(IApplicationSettings.ARCHIVED, Boolean.FALSE));
      }
      else {
        hbnCriteria.add(Restrictions.eq(IApplicationSettings.ARCHIVED, archived));
        hbnCountCriteria.add(Restrictions.eq(IApplicationSettings.ARCHIVED, archived));
      }
    }

    PersistenceUtil.applyCriteria(params, criteria);
    hbnHelper.applyParameters(params, hbnCriteria);

    try {
      hbnCountCriteria.setProjection(Projections.count(IApplicationSettings.KEY));
      session.flush();

      final long count = ((Number)hbnCountCriteria.uniqueResult()).longValue();

      if (count > 0) {
        entities.addAll(hbnCriteria.list());
      }

      return count;
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#clear(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List)
   */
  public void clear(final PersistenceSession session, final List<IQueryParameter> parameters)
      throws PersistenceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Removing entities using session = " + session + ", parameters = " + parameters);
    }

    session.removeEntities(
        session.findEntities(IApplicationSettings.class, QUERY_FIND, parameters), null);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#count(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List)
   */
  public long count(final PersistenceSession session, final List<IQueryParameter> parameters)
      throws PersistenceException {
    return helper.count(session, parameters, QUERY_FIND_COUNT);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
   * , java.util.List, java.util.List)
   */
  public long find(final PersistenceSession session, final List<IApplicationSettings> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    return helper.findListItems(session, entities, parameters, QUERY_FIND, QUERY_FIND_COUNT);
  }

  /*
   * See super class or interface. (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#findByCreationDate(org.infodavid.common.persistence
   * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
   * java.util.List)
   */
  public long findByCreationDate(final PersistenceSession session, final DateTime date1,
      final DateTime date2, final List<IApplicationSettings> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, IApplicationSettings.CREATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities, params,
          QUERY_FIND_BY_CREATION_DATE, QUERY_FIND_BY_CREATION_DATE_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * See super class or interface. (nonIUser-Javadoc)
   * @see
   * org.infodavid.common.persistence.IDataService#findByModificationDate(org.infodavid.common.persistence
   * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
   * java.util.List)
   */
  public long findByModificationDate(final PersistenceSession session, final DateTime date1,
      final DateTime date2, final List<IApplicationSettings> entities,
      final List<IQueryParameter> parameters) throws PersistenceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

    try {
      PersistenceUtil.applySorting(params, IApplicationSettings.MODIFICATION_DATE);

      return helper.findListItemsByDate(session, date1, date2, entities, params,
          QUERY_FIND_BY_MODIFICATION_DATE, QUERY_FIND_BY_MODIFICATION_DATE_COUNT);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * (non-Javadoc)
   * @see org.infodavid.common.impl.persistence.AbstractApplicationSettingsDataService#
   * doFindNotNullPropertyValue(org.infodavid.common.persistence.PersistenceSession, java.lang.String)
   */
  @Override
  protected String doFindNotNullPropertyValue(final PersistenceSession session,
      final String property) throws PersistenceException, ServiceException {
    PersistenceUtil.validate(session);

    final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(1);

    params.get(0).setName("property").setValue(property);

    try {
      session.flush(); // use of native query

      return (String)session.executeQuery(String.class, QUERY_FIND_GLOBAL_PROPERTY, params);
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.ApplicationSettingsDataService#updateProperties(org.infodavid.
   * common.persistence.PersistenceSession, long, java.util.Collection)
   */
  @Override
  public DateTime updateProperties(final PersistenceSession session, final String product,
      final Collection<IGenericProperty> properties) throws PersistenceException, ServiceException {
    final IApplicationSettings entity = findByProduct(session, product);

    if (entity == null) {
      throw new NoDataFoundException(getEntityClass(), product);
    }

    final IGenericPropertiesMap map = entity.getProperties();

    for (final IGenericProperty item : properties) {
      map.addData(item);
    }

    update(session, entity);

    return entity.getModificationDate();
  }

  /*
   * (non-Javadoc)
   * @see
   * org.infodavid.common.persistence.ApplicationSettingsDataService#findProperties(org.infodavid.common
   * .persistence.PersistenceSession, java.util.Collection, java.lang.String,
   * org.infodavid.common.model.criteria.IGenericDataCriteria)
   */
  @SuppressWarnings("unchecked")
  @Override
  public void findProperties(final PersistenceSession session,
      final Collection<IGenericProperty> results, final String product,
      final IGenericPropertyCriteria criteria) throws PersistenceException, ServiceException {
    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Searching properties by criteria using session = " + session + ", results = "
          + results + ", criteria = " + criteria);
    }

    PersistenceUtil.validate(session);
    ServiceUtil.validate(results);

    final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(3);

    params.get(0).setName(IApplicationSettings.PRODUCT).setValue(product);

    if (criteria == null) {
      params.get(1).setName(IGenericProperty.NAME).setValue(null);
      params.get(2).setName(IGenericProperty.SCOPE).setValue(null);
    }
    else {
      params.get(1).setName(IGenericProperty.NAME).setValue(criteria.getNamePattern());
      params.get(2).setName(IGenericProperty.SCOPE).setValue(criteria.getScopePattern());
    }

    try {
      session.flush();

      final List<IGenericProperty> properties =
          (List<IGenericProperty>)session.executeQuery(IGenericProperty.class,
              QUERY_FIND_PROPERTIES, params, false);

      if (!(properties == null || properties.isEmpty())) {
        results.addAll(properties);
      }
    }
    finally {
      PersistenceUtil.releaseQueryParameters(params);
    }
  }
}
